/*jslint eqeq: true, plusplus: true, undef: true, sloppy: true, vars: true, forin: true, nomen: true */
/*!
 * jQuery MobiScroll v2.6.2
 * http://mobiscroll.com
 *
 * Copyright 2010-2013, Acid Media
 * Licensed under the MIT license.
 *
 */
(function($) {

	function Scroller(elem, settings) {
		var m, hi, v, dw, ww, // Window width
		wh, // Window height
		rwh, mw, // Modal width
		mh, // Modal height
		lock, anim, debounce, theme, lang, click, scrollable, moved, start, startTime, stop, p, min, max, target, index, timer, readOnly, preventShow, that = this, ms = $.mobiscroll, e = elem, elm = $(e), s = extend(
				{}, defaults), pres = {}, iv = {}, pos = {}, pixels = {}, wheels = [], input = elm
				.is('input'), visible = false, onStart = function(e) {
			// Scroll start
			if (testTouch(e) && !move && !isReadOnly(this) && !click) {
				// Prevent scroll
				e.preventDefault();

				move = true;
				scrollable = s.mode != 'clickpick';
				target = $('.dw-ul', this);
				setGlobals(target);
				moved = iv[index] !== undefined; // Don't allow tap, if still
				// moving
				p = moved ? getCurrentPosition(target) : pos[index];
				start = getCoord(e, 'Y');
				startTime = new Date();
				stop = start;
				scroll(target, index, p, 0.001);

				if (scrollable) {
					target.closest('.dwwl').addClass('dwa');
				}

				$(document).on(MOVE_EVENT, onMove).on(END_EVENT, onEnd);
			}
		}, onMove = function(e) {
			if (scrollable) {
				e.preventDefault();
				e.stopPropagation();
				stop = getCoord(e, 'Y');
				scroll(target, index, constrain(p + (start - stop) / hi,
						min - 1, max + 1));
			}
			moved = true;
		}, onEnd = function(e) {
			var time = new Date() - startTime, val = constrain(p
					+ (start - stop) / hi, min - 1, max + 1), speed, dist, tindex, ttop = target
					.offset().top;

			if (time < 300) {
				speed = (stop - start) / time;
				dist = (speed * speed) / s.speedUnit;
				if (stop - start < 0) {
					dist = -dist;
				}
			} else {
				dist = stop - start;
			}

			tindex = Math.round(p - dist / hi);

			if (!dist && !moved) { // this is a "tap"
				var idx = Math.floor((stop - ttop) / hi), li = $('.dw-li',
						target).eq(idx), hl = scrollable;

				if (event('onValueTap', [ li ]) !== false) {
					tindex = idx;
				} else {
					hl = true;
				}

				if (hl) {
					li.addClass('dw-hl'); // Highlight
					setTimeout(function() {
						li.removeClass('dw-hl');
					}, 200);
				}
			}

			if (scrollable) {
				calc(target, tindex, 0, true, Math.round(val));
			}

			move = false;
			target = null;

			$(document).off(MOVE_EVENT, onMove).off(END_EVENT, onEnd);
		}, onBtnStart = function(e) {
			var btn = $(this);
			$(document).on(END_EVENT, onBtnEnd);
			// Active button
			if (!btn.hasClass('dwb-d')) {
				btn.addClass('dwb-a');
			}
			setTimeout(function() {
				btn.blur();
			}, 10);
			// +/- buttons
			if (btn.hasClass('dwwb')) {
				if (testTouch(e)) {
					step(e, btn.closest('.dwwl'), btn.hasClass('dwwbp') ? plus
							: minus);
				}
			}
		}, onBtnEnd = function(e) {
			if (click) {
				clearInterval(timer);
				click = false;
			}
			$(document).off(END_EVENT, onBtnEnd);
			$('.dwb-a', dw).removeClass('dwb-a');
		}, onKeyDown = function(e) {
			if (e.keyCode == 38) { // up
				step(e, $(this), minus);
			} else if (e.keyCode == 40) { // down
				step(e, $(this), plus);
			}
		}, onKeyUp = function(e) {
			if (click) {
				clearInterval(timer);
				click = false;
			}
		}, onScroll = function(e) {
			if (!isReadOnly(this)) {
				e.preventDefault();
				e = e.originalEvent || e;
				var delta = e.wheelDelta ? (e.wheelDelta / 120)
						: (e.detail ? (-e.detail / 3) : 0), t = $('.dw-ul',
						this);

				setGlobals(t);
				calc(t, Math.round(pos[index] - delta), delta < 0 ? 1 : 2);
			}
		};

		// Private functions

		function step(e, w, func) {
			e.stopPropagation();
			e.preventDefault();
			if (!click && !isReadOnly(w) && !w.hasClass('dwa')) {
				click = true;
				// + Button
				var t = w.find('.dw-ul');

				setGlobals(t);
				clearInterval(timer);
				timer = setInterval(function() {
					func(t);
				}, s.delay);
				func(t);
			}
		}

		function isReadOnly(wh) {
			if ($.isArray(s.readonly)) {
				var i = $('.dwwl', dw).index(wh);
				return s.readonly[i];
			}
			return s.readonly;
		}

		function generateWheelItems(i) {
			var html = '<div class="dw-bf">', ww = wheels[i], w = ww.values ? ww
					: convert(ww), l = 1, labels = w.labels || [], values = w.values, keys = w.keys
					|| values;

			$
					.each(
							values,
							function(j, v) {
								if (l % 20 == 0) {
									html += '</div><div class="dw-bf">';
								}
								html += '<div role="option" aria-selected="false" class="dw-li dw-v" data-val="'
										+ keys[j]
										+ '"'
										+ (labels[j] ? ' aria-label="'
												+ labels[j] + '"' : '')
										+ ' style="height:'
										+ hi
										+ 'px;line-height:'
										+ hi
										+ 'px;"><div class="dw-i">'
										+ v
										+ '</div></div>';
								l++;
							});

			html += '</div>';
			return html;
		}

		function setGlobals(t) {
			min = $('.dw-li', t).index($('.dw-v', t).eq(0));
			max = $('.dw-li', t).index($('.dw-v', t).eq(-1));
			index = $('.dw-ul', dw).index(t);
		}

		function formatHeader(v) {
			var t = s.headerText;
			return t ? (typeof t === 'function' ? t.call(e, v) : t.replace(
					/\{value\}/i, v)) : '';
		}

		function read() {
			that.temp = ((input && that.val !== null && that.val != elm.val()) || that.values === null) ? s
					.parseValue(elm.val() || '', that)
					: that.values.slice(0);
			setVal();
		}

		function getCurrentPosition(t) {
			var style = window.getComputedStyle ? getComputedStyle(t[0])
					: t[0].style, matrix, px;

			if (has3d) {
				$.each([ 't', 'webkitT', 'MozT', 'OT', 'msT' ], function(i, v) {
					if (style[v + 'ransform'] !== undefined) {
						matrix = style[v + 'ransform'];
						return false;
					}
				});
				matrix = matrix.split(')')[0].split(', ');
				px = matrix[13] || matrix[5];
			} else {
				px = style.top.replace('px', '');
			}

			return Math.round(m - (px / hi));
		}

		function ready(t, i) {
			clearTimeout(iv[i]);
			delete iv[i];
			t.closest('.dwwl').removeClass('dwa');
		}

		function scroll(t, index, val, time, active) {

			var px = (m - val) * hi, style = t[0].style, i;

			if (px == pixels[index] && iv[index]) {
				return;
			}

			if (time && px != pixels[index]) {
				// Trigger animation start event
				event('onAnimStart', [ dw, index, time ]);
			}

			pixels[index] = px;

			style[pr + 'Transition'] = 'all ' + (time ? time.toFixed(3) : 0)
					+ 's ease-out';

			if (has3d) {
				style[pr + 'Transform'] = 'translate3d(0,' + px + 'px,0)';
			} else {
				style.top = px + 'px';
			}

			if (iv[index]) {
				ready(t, index);
			}

			if (time && active) {
				t.closest('.dwwl').addClass('dwa');
				iv[index] = setTimeout(function() {
					ready(t, index);
				}, time * 1000);
			}

			pos[index] = val;
		}

		function scrollToPos(time, index, manual, dir, active) {

			// Call validation event
			if (event('validate', [ dw, index, time ]) !== false) {

				// Set scrollers to position
				$('.dw-ul', dw)
						.each(
								function(i) {
									var t = $(this), cell = $(
											'.dw-li[data-val="' + that.temp[i]
													+ '"]', t), cells = $(
											'.dw-li', t), v = cells.index(cell), l = cells.length, sc = i == index
											|| index === undefined;

									// Scroll to a valid cell
									if (!cell.hasClass('dw-v')) {
										var cell1 = cell, cell2 = cell, dist1 = 0, dist2 = 0;

										while (v - dist1 >= 0
												&& !cell1.hasClass('dw-v')) {
											dist1++;
											cell1 = cells.eq(v - dist1);
										}

										while (v + dist2 < l
												&& !cell2.hasClass('dw-v')) {
											dist2++;
											cell2 = cells.eq(v + dist2);
										}

										// If we have direction (+/- or mouse
										// wheel), the distance does not count
										if (((dist2 < dist1 && dist2 && dir !== 2)
												|| !dist1 || (v - dist1 < 0) || dir == 1)
												&& cell2.hasClass('dw-v')) {
											cell = cell2;
											v = v + dist2;
										} else {
											cell = cell1;
											v = v - dist1;
										}
									}

									if (!(cell.hasClass('dw-sel')) || sc) {
										// Set valid value
										that.temp[i] = cell.attr('data-val');

										// Add selected class to cell
										$('.dw-sel', t).removeClass('dw-sel');

										if (!s.multiple) {
											$('.dw-sel', t).removeAttr(
													'aria-selected');
											cell.attr('aria-selected', 'true');
										}

										cell.addClass('dw-sel');

										// Scroll to position
										scroll(t, i, v, sc ? time : 0.1,
												sc ? active : false);
									}
								});

				// Reformat value if validation changed something
				v = s.formatResult(that.temp);
				if (s.display == 'inline') {
					setVal(manual, 0, true);
				} else {
					$('.dwv', dw).html(formatHeader(v));
				}

				if (manual) {
					event('onChange', [ v ]);
				}
			}

		}

		function event(name, args) {
			var ret;
			args.push(that);
			$.each([ theme.defaults, pres, settings ], function(i, v) {
				if (v[name]) { // Call preset event
					ret = v[name].apply(e, args);
				}
			});
			return ret;
		}

		function calc(t, val, dir, anim, orig) {
			val = constrain(val, min, max);

			var cell = $('.dw-li', t).eq(val), o = orig === undefined ? val
					: orig, idx = index, time = anim ? (val == o ? 0.1 : Math
					.abs((val - o) * s.timeUnit)) : 0;

			// Set selected scroller value
			that.temp[idx] = cell.attr('data-val');

			scroll(t, idx, val, time, orig);

			setTimeout(function() {
				// Validate
				scrollToPos(time, idx, true, dir, orig !== undefined);
			}, 10);
		}

		function plus(t) {
			var val = pos[index] + 1;
			calc(t, val > max ? min : val, 1, true);
		}

		function minus(t) {
			var val = pos[index] - 1;
			calc(t, val < min ? max : val, 2, true);
		}

		function setVal(fill, time, noscroll, temp) {

			if (visible && !noscroll) {
				scrollToPos(time);
			}

			v = s.formatResult(that.temp);

			if (!temp) {
				that.values = that.temp.slice(0);
				that.val = v;
			}

			if (fill) {
				if (input) {
					elm.val(v).trigger('change');
				}
			}
		}

		// Public functions

		that.position = function(check) {

			if (s.display == 'inline'
					|| (ww === $(window).width() && rwh === $(window).height() && check)
					|| (event('onPosition', [ dw ]) === false)) {
				return;
			}

			var w, l, t, aw, // anchor width
			ah, // anchor height
			ap, // anchor position
			at, // anchor top
			al, // anchor left
			arr, // arrow
			arrw, // arrow width
			arrl, // arrow left
			scroll, totalw = 0, minw = 0, st = $(window).scrollTop(), wr = $(
					'.dwwr', dw), d = $('.dw', dw), css = {}, anchor = s.anchor === undefined ? elm
					: s.anchor;

			ww = $(window).width();
			rwh = $(window).height();
			wh = window.innerHeight; // on iOS we need innerHeight
			wh = wh || rwh;

			if (/modal|bubble/.test(s.display)) {
				$('.dwc', dw).each(function() {
					w = $(this).outerWidth(true);
					totalw += w;
					minw = (w > minw) ? w : minw;
				});
				w = totalw > ww ? minw : totalw;
				wr.width(w).css('white-space', totalw > ww ? '' : 'nowrap');
			}

			mw = d.outerWidth();
			mh = d.outerHeight(true);

			lock = mh <= wh && mw <= ww;

			if (s.display == 'modal') {
				l = (ww - mw) / 2;
				t = st + (wh - mh) / 2;
			} else if (s.display == 'bubble') {
				scroll = true;
				arr = $('.dw-arrw-i', dw);
				ap = anchor.offset();
				at = ap.top;
				al = ap.left;

				// horizontal positioning
				aw = anchor.outerWidth();
				ah = anchor.outerHeight();
				l = al - (d.outerWidth(true) - aw) / 2;
				l = l > (ww - mw) ? (ww - (mw + 20)) : l;
				l = l >= 0 ? l : 20;

				// vertical positioning
				t = at - mh; // above the input
				if ((t < st) || (at > st + wh)) { // if doesn't fit above or
					// the input is out of the
					// screen
					d.removeClass('dw-bubble-top').addClass('dw-bubble-bottom');
					t = at + ah; // below the input
				} else {
					d.removeClass('dw-bubble-bottom').addClass('dw-bubble-top');
				}

				// Calculate Arrow position
				arrw = arr.outerWidth();
				arrl = al + aw / 2 - (l + (mw - arrw) / 2);

				// Limit Arrow position
				$('.dw-arr', dw).css({
					left : constrain(arrl, 0, arrw)
				});
			} else {
				css.width = '100%';
				if (s.display == 'top') {
					t = st;
				} else if (s.display == 'bottom') {
					t = st + wh - mh;
				}
			}

			css.top = t < 0 ? 0 : t;
			css.left = l;
			d.css(css);

			// If top + modal height > doc height, increase doc height
			$('.dw-persp', dw).height(0).height(
					t + mh > $(document).height() ? t + mh : $(document)
							.height());

			// Scroll needed
			if (scroll && ((t + mh > st + wh) || (at > st + wh))) {
				$(window).scrollTop(t + mh - wh);
			}
		};

		/**
		 * Enables the scroller and the associated input.
		 */
		that.enable = function() {
			s.disabled = false;
			if (input) {
				elm.prop('disabled', false);
			}
		};

		/**
		 * Disables the scroller and the associated input.
		 */
		that.disable = function() {
			s.disabled = true;
			if (input) {
				elm.prop('disabled', true);
			}
		};

		/**
		 * Gets the selected wheel values, formats it, and set the value of the
		 * scroller instance. If input parameter is true, populates the
		 * associated input element.
		 * 
		 * @param {Array}
		 *            values - Wheel values.
		 * @param {Boolean}
		 *            [fill=false] - Also set the value of the associated input
		 *            element.
		 * @param {Number}
		 *            [time=0] - Animation time
		 * @param {Boolean}
		 *            [temp=false] - If true, then only set the temporary
		 *            value.(only scroll there but not set the value)
		 */
		that.setValue = function(values, fill, time, temp) {
			that.temp = $.isArray(values) ? values.slice(0) : s.parseValue
					.call(e, values + '', that);
			setVal(fill, time, false, temp);
		};

		that.getValue = function() {
			return that.values;
		};

		that.getValues = function() {
			var ret = [], i;

			for (i in that._selectedValues) {
				ret.push(that._selectedValues[i]);
			}
			return ret;
		};

		/**
		 * Changes the values of a wheel, and scrolls to the correct position
		 */
		that.changeWheel = function(idx, time) {
			if (dw) {
				var i = 0, nr = idx.length;

				$.each(s.wheels, function(j, wg) {
					$.each(wg, function(k, w) {
						if ($.inArray(i, idx) > -1) {
							wheels[i] = w;
							$('.dw-ul', dw).eq(i).html(generateWheelItems(i));
							nr--;
							if (!nr) {
								that.position();
								scrollToPos(time, undefined, true);
								return false;
							}
						}
						i++;
					});
					if (!nr) {
						return false;
					}
				});
			}
		};

		/**
		 * Return true if the scroller is currently visible.
		 */
		that.isVisible = function() {
			return visible;
		};

		that.tap = function(el, handler) {
			var startX, startY;

			if (s.tap) {
				el
						.on('touchstart.dw', function(e) {
							e.preventDefault();
							startX = getCoord(e, 'X');
							startY = getCoord(e, 'Y');
						})
						.on(
								'touchend.dw',
								function(e) {
									// If movement is less than 20px, fire the
									// click event handler
									if (Math.abs(getCoord(e, 'X') - startX) < 20
											&& Math.abs(getCoord(e, 'Y')
													- startY) < 20) {
										handler.call(this, e);
									}
									tap = true;
									setTimeout(function() {
										tap = false;
									}, 300);
								});
			}

			el.on('click.dw', function(e) {
				if (!tap) {
					// If handler was not called on touchend, call it on click;
					handler.call(this, e);
				}
			});

		};

		/**
		 * Shows the scroller instance.
		 * 
		 * @param {Boolean}
		 *            prevAnim - Prevent animation if true
		 */
		that.show = function(prevAnim) {
			if (s.disabled || visible) {
				return false;
			}

			if (s.display == 'top') {
				anim = 'slidedown';
			}

			if (s.display == 'bottom') {
				anim = 'slideup';
			}

			// Parse value from input
			read();

			event('onBeforeShow', []);

			// Create wheels
			var lbl, l = 0, mAnim = '';

			if (anim && !prevAnim) {
				mAnim = 'dw-' + anim + ' dw-in';
			}
			// Create wheels containers
			var html = '<div role="dialog" class="'
					+ s.theme
					+ ' dw-'
					+ s.display
					+ (prefix ? ' dw' + prefix : '')
					+ '">'
					+ (s.display == 'inline' ? '<div class="dw dwbg dwi"><div class="dwwr">'
							: '<div class="dw-persp"><div class="dwo"></div><div class="dw dwbg '
									+ mAnim
									+ '"><div class="dw-arrw"><div class="dw-arrw-i"><div class="dw-arr"></div></div></div><div class="dwwr"><div aria-live="assertive" class="dwv'
									+ (s.headerText ? '' : ' dw-hidden')
									+ '"></div>') + '<div class="dwcc">';

			$
					.each(
							s.wheels,
							function(i, wg) { // Wheel groups
								html += '<div class="dwc'
										+ (s.mode != 'scroller' ? ' dwpm'
												: ' dwsc')
										+ (s.showLabel ? '' : ' dwhl')
										+ '"><div class="dwwc dwrc"><table cellpadding="0" cellspacing="0"><tr>';
								$
										.each(
												wg,
												function(j, w) { // Wheels
													wheels[l] = w;
													lbl = w.label !== undefined ? w.label
															: j;
													html += '<td><div class="dwwl dwrc dwwl'
															+ l
															+ '">'
															+ (s.mode != 'scroller' ? '<div class="dwb-e dwwb dwwbp" style="height:'
																	+ hi
																	+ 'px;line-height:'
																	+ hi
																	+ 'px;"><span>+</span></div><div class="dwb-e dwwb dwwbm" style="height:'
																	+ hi
																	+ 'px;line-height:'
																	+ hi
																	+ 'px;"><span>&ndash;</span></div>'
																	: '')
															+ '<div class="dwl">'
															+ lbl
															+ '</div><div tabindex="0" aria-live="off" aria-label="'
															+ lbl
															+ '" role="listbox" class="dwww"><div class="dww" style="height:'
															+ (s.rows * hi)
															+ 'px;min-width:'
															+ s.width
															+ 'px;"><div class="dw-ul">';
													// Create wheel values
													html += generateWheelItems(l);
													html += '</div><div class="dwwol"></div></div><div class="dwwo"></div></div><div class="dwwol"></div></div></td>';
													l++;
												});

								html += '</tr></table></div></div>';
							});

			html += '</div>'
					+ (s.display != 'inline' ? '<div class="dwbc'
							+ (s.button3 ? ' dwbc-p' : '')
							+ '"><span class="dwbw dwb-s"><span class="dwb dwb-e" role="button" tabindex="0">'
							+ s.setText
							+ '</span></span>'
							+ (s.button3 ? '<span class="dwbw dwb-n"><span class="dwb dwb-e" role="button" tabindex="0">'
									+ s.button3Text + '</span></span>'
									: '')
							+ '<span class="dwbw dwb-c"><span class="dwb dwb-e" role="button" tabindex="0">'
							+ s.cancelText + '</span></span></div></div>'
							: '') + '</div></div></div>';
			dw = $(html);

			scrollToPos();

			event('onMarkupReady', [ dw ]);

			// Show
			if (s.display != 'inline') {
				dw.appendTo('body');
				if (anim && !prevAnim) {
					dw.addClass('dw-trans');
					// Remove animation class
					setTimeout(function() {
						dw.removeClass('dw-trans').find('.dw').removeClass(
								mAnim);
					}, 350);
				}
			} else if (elm.is('div')) {
				elm.html(dw);
			} else {
				dw.insertAfter(elm);
			}

			event('onMarkupInserted', [ dw ]);

			visible = true;

			// Theme init
			theme.init(dw, that);

			if (s.display != 'inline') {
				// Init buttons
				that.tap($('.dwb-s span', dw), function() {
					that.select();
				});

				that.tap($('.dwb-c span', dw), function() {
					that.cancel();
				});

				if (s.button3) {
					that.tap($('.dwb-n span', dw), s.button3);
				}

				// Enter / ESC
				$(window).on('keydown.dw', function(e) {
					if (e.keyCode == 13) {
						that.select();
					} else if (e.keyCode == 27) {
						that.cancel();
					}
				});

				// Prevent scroll if not specified otherwise
				if (s.scrollLock) {
					dw.on('touchmove touchstart', function(e) {
						if (lock) {
							e.preventDefault();
						}
					});
				}

				// Disable inputs to prevent bleed through (Android bug) and set
				// autocomplete to off (for Firefox)
				$('input,select,button').each(
						function() {
							if (!this.disabled) {
								if ($(this).attr('autocomplete')) {
									$(this).data('autocomplete',
											$(this).attr('autocomplete'));
								}
								$(this).addClass('dwtd').prop('disabled', true)
										.attr('autocomplete', 'off');
							}
						});

				// Set position
				that.position();
				$(window).on('orientationchange.dw resize.dw scroll.dw',
						function(e) {
							// Sometimes scrollTop is not correctly set, so we
							// wait a little
							clearTimeout(debounce);
							debounce = setTimeout(function() {
								var scroll = e.type == 'scroll';
								if ((scroll && lock) || !scroll) {
									that.position(!scroll);
								}
							}, 100);
						});

				that.alert(s.ariaDesc);
			}

			// Events
			$('.dwwl', dw).on('DOMMouseScroll mousewheel', onScroll).on(
					START_EVENT, onStart).on('keydown', onKeyDown).on('keyup',
					onKeyUp);

			dw.on(START_EVENT, '.dwb-e', onBtnStart).on('keydown', '.dwb-e',
					function(e) {
						if (e.keyCode == 32) { // Space
							e.preventDefault();
							e.stopPropagation();
							$(this).click();
						}
					});

			event('onShow', [ dw, v ]);
		};

		/**
		 * Hides the scroller instance.
		 */
		that.hide = function(prevAnim, btn) {
			// If onClose handler returns false, prevent hide
			if (!visible || event('onClose', [ v, btn ]) === false) {
				return false;
			}

			// Re-enable temporary disabled fields
			$('.dwtd').each(function() {
				$(this).prop('disabled', false).removeClass('dwtd');
				if ($(this).data('autocomplete')) {
					$(this).attr('autocomplete', $(this).data('autocomplete'));
				} else {
					$(this).removeAttr('autocomplete');
				}
			});

			// Hide wheels and overlay
			if (dw) {
				if (s.display != 'inline' && anim && !prevAnim) {
					dw.addClass('dw-trans').find('.dw').addClass(
							'dw-' + anim + ' dw-out');
					setTimeout(function() {
						dw.remove();
						dw = null;
					}, 350);
				} else {
					dw.remove();
					dw = null;
				}

				// Stop positioning on window resize
				$(window).off('.dw');
			}

			pixels = {};
			visible = false;
			preventShow = true;

			elm.focus();
		};

		that.select = function() {
			if (that.hide(false, 'set') !== false) {
				setVal(true, 0, true);
				event('onSelect', [ that.val ]);
			}
		};

		that.alert = function(txt) {
			aria.text(txt);
			clearTimeout(alertTimer);
			alertTimer = setTimeout(function() {
				aria.text('');
			}, 5000);
		};

		/**
		 * Cancel and hide the scroller instance.
		 */
		that.cancel = function() {
			if (that.hide(false, 'cancel') !== false) {
				event('onCancel', [ that.val ]);
			}
		};

		/**
		 * Scroller initialization.
		 */
		that.init = function(ss) {
			// Get theme defaults
			theme = extend({
				defaults : {},
				init : empty
			}, ms.themes[ss.theme || s.theme]);

			// Get language defaults
			lang = ms.i18n[ss.lang || s.lang];

			extend(settings, ss); // Update original user settings
			extend(s, theme.defaults, lang, settings);

			that.settings = s;

			// Unbind all events (if re-init)
			elm.off('.dw');

			var preset = ms.presets[s.preset];

			if (preset) {
				pres = preset.call(e, that);
				extend(s, pres, settings); // Load preset settings
			}

			// Set private members
			m = Math.floor(s.rows / 2);
			hi = s.height;
			anim = s.animate;

			if (visible) {
				that.hide();
			}

			if (s.display == 'inline') {
				that.show();
			} else {
				read();
				if (input) {
					// Set element readonly, save original state
					if (readOnly === undefined) {
						readOnly = e.readOnly;
					}
					e.readOnly = true;
					// Init show datewheel
					if (s.showOnFocus) {
						elm.on('focus.dw', function() {
							if (!preventShow) {
								that.show();
							}
							preventShow = false;
						});
					}
				}
				if (s.showOnTap) {
					that.tap(elm, function() {
						that.show();
					});
				}
			}
		};

		that.trigger = function(name, params) {
			return event(name, params);
		};

		that.option = function(opt, value) {
			var obj = {};
			if (typeof opt === 'object') {
				obj = opt;
			} else {
				obj[opt] = value;
			}
			that.init(obj);
		};

		that.destroy = function() {
			that.hide();
			elm.off('.dw');
			delete scrollers[e.id];
			if (input) {
				e.readOnly = readOnly;
			}
		};

		that.getInst = function() {
			return that;
		};

		that.values = null;
		that.val = null;
		that.temp = null;
		that._selectedValues = {};

		that.init(settings);
	}

	function testProps(props) {
		var i;
		for (i in props) {
			if (mod[props[i]] !== undefined) {
				return true;
			}
		}
		return false;
	}

	function testPrefix() {
		var prefixes = [ 'Webkit', 'Moz', 'O', 'ms' ], p;

		for (p in prefixes) {
			if (testProps([ prefixes[p] + 'Transform' ])) {
				return '-' + prefixes[p].toLowerCase();
			}
		}
		return '';
	}

	function testTouch(e) {
		if (e.type === 'touchstart') {
			touch = true;
			/*
			 * setTimeout(function () { touch = false; // Reset if mouse event
			 * was not fired }, 500);
			 */
		} else if (touch) {
			touch = false;
			return false;
		}
		return true;
	}

	function getCoord(e, c) {
		var org = e.originalEvent, ct = e.changedTouches;
		return ct || (org && org.changedTouches) ? (org ? org.changedTouches[0]['page'
				+ c]
				: ct[0]['page' + c])
				: e['page' + c];

	}

	function constrain(val, min, max) {
		return Math.max(min, Math.min(val, max));
	}

	function convert(w) {
		var ret = {
			values : [],
			keys : []
		};
		$.each(w, function(k, v) {
			ret.keys.push(k);
			ret.values.push(v);
		});
		return ret;
	}

	function init(that, options, args) {
		var ret = that;

		// Init
		if (typeof options === 'object') {
			return that.each(function() {
				if (!this.id) {
					uuid += 1;
					this.id = 'mobiscroll' + uuid;
				}
				scrollers[this.id] = new Scroller(this, options);
			});
		}

		// Method call
		if (typeof options === 'string') {
			that.each(function() {
				var r, inst = scrollers[this.id];

				if (inst && inst[options]) {
					r = inst[options].apply(this, Array.prototype.slice.call(
							args, 1));
					if (r !== undefined) {
						ret = r;
						return false;
					}
				}
			});
		}

		return ret;
	}

	var move, tap, touch, alertTimer, aria, date = new Date(), uuid = date
			.getTime(), scrollers = {}, empty = function() {
	}, mod = document.createElement('modernizr').style, has3d = testProps([
			'perspectiveProperty', 'WebkitPerspective', 'MozPerspective',
			'OPerspective', 'msPerspective' ]), prefix = testPrefix(), pr = prefix
			.replace(/^\-/, '').replace('moz', 'Moz'), extend = $.extend, START_EVENT = 'touchstart mousedown', MOVE_EVENT = 'touchmove mousemove', END_EVENT = 'touchend mouseup', defaults = {
		// Options
		width : 70,
		height : 40,
		rows : 3,
		delay : 300,
		disabled : false,
		readonly : false,
		showOnFocus : true,
		showOnTap : true,
		showLabel : true,
		wheels : [],
		theme : '',
		headerText : '{value}',
		display : 'modal',
		mode : 'scroller',
		preset : '',
		lang : 'en-US',
		setText : 'Set',
		cancelText : 'Cancel',
		ariaDesc : 'Select a value',
		scrollLock : true,
		tap : true,
		speedUnit : 0.0012,
		timeUnit : 0.1,
		formatResult : function(d) {
			return d.join(' ');
		},
		parseValue : function(value, inst) {
			var val = value.split(' '), ret = [], i = 0, keys;

			$.each(inst.settings.wheels, function(j, wg) {
				$.each(wg, function(k, w) {
					w = w.values ? w : convert(w);
					keys = w.keys || w.values;
					if ($.inArray(val[i], keys) !== -1) {
						ret.push(val[i]);
					} else {
						ret.push(keys[0]);
					}
					i++;
				});
			});
			return ret;
		}
	};

	$(function() {
		aria = $('<div class="dw-hidden" role="alert"></div>').appendTo('body');
	});

	$(document).on('mouseover mouseup mousedown click', function(e) { // Prevent
		// standard
		// behaviour
		// on
		// body
		// click
		if (tap) {
			e.stopPropagation();
			e.preventDefault();
			return false;
		}
	});

	$.fn.mobiscroll = function(method) {
		extend(this, $.mobiscroll.shorts);
		return init(this, method, arguments);
	};

	$.mobiscroll = $.mobiscroll || {
		/**
		 * Set settings for all instances.
		 * 
		 * @param {Object}
		 *            o - New default settings.
		 */
		setDefaults : function(o) {
			extend(defaults, o);
		},
		presetShort : function(name) {
			this.shorts[name] = function(method) {
				return init(this, extend(method, {
					preset : name
				}), arguments);
			};
		},
		has3d : has3d,
		shorts : {},
		presets : {},
		themes : {},
		i18n : {}
	};

	$.scroller = $.scroller || $.mobiscroll;
	$.fn.scroller = $.fn.scroller || $.fn.mobiscroll;

})(jQuery);
